package com.plasticcode.performance;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.AnnotationConfiguration;
import org.junit.BeforeClass;
import org.junit.Test;

import com.plasticcode.log.domain.Run;
import com.plasticcode.log.domain.Runner;

public class HibernateTestCase {    
    private static SessionFactory sSessionFactory;

    @BeforeClass
    public static void initialiseSessionFactory() {
        sSessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    }
    

    @Test
    public void testInsertPerformance() throws Exception {
        PerformanceController controller = new PerformanceController(
                "Hibernate insert performance", new MeasuredOperation() {
            public void setup(int children) {}

            public void run(int children, int samples) {
                insert(children, samples);                
            }
        });
        controller.measure();
    }
    
    private Runner insert(int numOfChildren, int samples) {
        Runner runner = null;

        for (int i = 0; i < samples; i++) {
            Session session = sSessionFactory.getCurrentSession();
            session.beginTransaction();
    
            runner = TestDataHelper.createRunner();
            
            for (int j = 0; j < numOfChildren; j++) {
                Run run = TestDataHelper.createRun();
                runner.addRun(run);
            }
            
            session.save(runner);
            session.getTransaction().commit();
        }
        
        return runner;
    }

    @Test
    public void testUpdatePerformance() throws Exception {
        PerformanceController controller = new PerformanceController(
                "Hibernate update performance", new MeasuredOperation() {
            Runner runner;
            
            public void setup(int children) {
                runner = insert(children, 1);
            }

            public void run(int children, int samples) {
                update(runner, samples);
            }
        });
        controller.measure();
    }
    
    private void update(Runner runner, int samples) {
        for (int i = 0; i < samples; i++) {
            Session session = sSessionFactory.getCurrentSession();
            session.beginTransaction();
            
            TestDataHelper.mutateRunner(runner, i);
            for (Run run : runner.getRuns()) {
                TestDataHelper.mutateRun(run, i);
            }
            
            session.update(runner);
            session.getTransaction().commit();
        }
    }

    @Test
    public void testQueryUsingHSQL() throws Exception {
        PerformanceController controller = new PerformanceController(
                "Hibernate retrieve performance (HSQL)", new MeasuredOperation() {
            Runner runner;
            
            public void setup(int children) {
                runner = insert(children, 1);
            }

            public void run(int children, int samples) {
                queryHSQL(runner, samples);        
            }
        });
        controller.measure();
    }
    
    private void queryHSQL(Runner runner, int samples) {        
        for (int i = 0; i < samples; i++) {
            Session session = sSessionFactory.getCurrentSession();
            session.beginTransaction();
    
            // Using a HSQL query does not get loaded from the cache
            runner = (Runner) session.createQuery("from Runner r where r.id = ?").
                setLong(0, runner.getId()).setReadOnly(true).uniqueResult();
            runner.getName();
            for (Run run : runner.getRuns()) {
                run.getDistance();
            } 
            
            session.getTransaction().commit();
        }
    }
    

    @Test
    public void testQueryUsingCache() throws Exception {
        PerformanceController controller = new PerformanceController(
                "Hibernate retrieve performance (cache)", new MeasuredOperation() {
            Runner runner;
            
            public void setup(int children) {
                runner = insert(children, 1);
            }

            public void run(int children, int samples) {
                queryUsingCache(runner, samples);        
            }
        });
        controller.measure();
    }
    
    private void queryUsingCache(Runner runner, int samples) {        
        for (int i = 0; i < samples; i++) {
            Session session = sSessionFactory.getCurrentSession();
            session.beginTransaction();
                         
            // Using a HSQL query does not get loaded from the cache
            runner = (Runner) session.get(Runner.class, runner.getId());
            for (Run run : runner.getRuns()) {
                run.getDistance();
            }
            
            session.getTransaction().commit();
        }
    }
    
    @Test
    public void testMerge() throws Exception {
        PerformanceController controller = new PerformanceController(
                "Hibernate dirty merge performance", new MeasuredOperation() {
            Runner runner;
            
            public void setup(int children) {
                runner = insert(children, 1);
            }

            public void run(int children, int samples) {
                Session session = sSessionFactory.getCurrentSession();
                session.beginTransaction();
                runner = (Runner) session.load(Runner.class, runner.getId());
                dirtyCheck(session, runner, samples);
                session.getTransaction().commit();
            }
        });
        controller.measure();
    }

    private void dirtyCheck(Session session, Runner runner, int samples) {
        for (int i = 0; i < samples; i++) {
            session.merge(runner);
        }
    }
}
